﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization Custom Tool
//
// SVN revision information:
//   $Revision: 100 $
//------------------------------------------------------------------------------
using System;
using System.CodeDom;
using System.IO;
using FRAFV.Binary.Serialization.CodeDom;

namespace FRAFV.Binary.Serialization
{
	/// <summary>
	/// public void WriteBinary(BinaryWriter writer)
	/// {
	/// 	writer.WriteType1((Type1)Property1();
	/// 	...
	/// 	if (Checked2)
	/// 		writer.WriteType2((Type2)Property2();
	/// 	...
	/// }
	/// </summary>
	internal sealed class WriteBinaryGenerator : ClassGenerator.MethodGenerator
	{
		public WriteBinaryGenerator(ClassParser cls)
			: base(cls) { }

		protected override ClassGenerator.MethodContext CreateMember(ClassParser cls)
		{
			//void WriteBinary(BinaryWriter writer)
			var code = new CodeMemberMethod
			{
				Name = "WriteBinary"
			};
			code.Parameters.Add<BinaryWriter>("writer");
			return new MethodContext(cls, code);
		}

		protected override void Init()
		{
			if (!context.Class.BinBlock.WriteMethod.IsNullEmpty())
			{
				//WriteMethod;
				Code.Snippet(context.Class.BinBlock.WriteMethod);
			}
			if (GenerateBaseCall)
			{
				//base.WriteBinary(writer);
				Code.BaseMethodInvoke("WriteBinary", "writer".AsArgument());
			}
			if (HasEncoding)
			{
				Code
					//var encoding = Encoding;
					.Variable("encoding", GetEncoding())
					//var encoder = new BinaryReader(reader.BaseStream, encoding);
					.Variable("encoder", context.Class.BinBlock.ResolveBinaryWriterType().AsType().CreateInvoke(
						"writer".AsArgument().Property("BaseStream"), "encoding".AsVariable()));
			}
		}

		private sealed class MethodContext : ClassGenerator.MethodContext
		{
			public MethodContext(ClassParser cls, CodeMemberMethod member)
				: base(cls, member, member.Statements)
			{
				member.Attributes = MethodsAccess;
			}

			protected override string ResolveItemMethod()
			{
				return Class.BinBlock.WriteItemMethod;
			}
		}

		protected override ClassGenerator.PropertyGenerator CreateProperty(ClassParser.ClassProperty prop)
		{
			var context = new PropertyContext(this.context, prop);
			if (context.IsCustom)
			{
				return new PropertyCustomGenerator(context);
			}
			else if (context.IsItem)
			{
				return CreateItem(context);
			}
			else if (context.IsArrayItem)
			{
				return new PropertyArrayItemGenerator(context, CreateDataArray(context));
			}
			else
			{
				return new PropertyDataGenerator(context, CreateDataArray(context));
			}
		}

		private ClassGenerator.PropertyGenerator CreateDataArray(PropertyContext context)
		{
			if (!context.IsArray) return null;
			if (context.IsConditionArray)
				return new PropertyCollectionCustomGenerator(context);
			else if (context.CountHasStorage)
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionGenerator(context);
				else
					return new PropertyArrayGenerator(context);
			}
			else
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionCustomGenerator(context);
				else if (context.CountIsNumber)
					return new PropertyArrayNumGenerator(context);
				else
					return new PropertyArrayCustomGenerator(context);
			}
		}

		private ClassGenerator.PropertyItemGenerator CreateItem(PropertyContext context)
		{
			if (this.HasItemMethod)
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertyAnyItemGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceItemGenerator(context, CreateItemArray(context));
						break;
					case BinItemOrder.Any:
						return new PropertyAnyItemGenerator(context, CreateItemArray(context)); //single&repeat
				}
			else
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertySequenceSampleGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceSampleGenerator(context, CreateItemArray(context));
						break;
				}
			throw new NotSupportedException();
		}

		private ClassGenerator.PropertyGenerator CreateItemArray(PropertyContext context)
		{
			return null;
		}

		private sealed class PropertyContext : ClassGenerator.PropertyContext
		{
			public PropertyContext(ClassGenerator.MethodContext context, ClassParser.ClassProperty prop)
				: base(context, prop) { }

			protected override string ResolveCountMethod(out Type arg)
			{
				return Property.BinArray.CountFormat.ResolveWrite(out arg);
			}
		}

		private sealed class PropertyDataGenerator : ClassGenerator.PropertyDataGenerator
		{
			public PropertyDataGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override ClassGenerator.FormatGenerator CreateFormat(BinDataAttribute binData)
			{
				var context = new FormatContext(this.context, binData);
				if (!context.Format.HasLength())
					return new FormatPrimitiveGenerator(context);
				else if (context.LengthHasStorage)
					return new FormatLongGenerator(context);
				else if (!context.LengthIsNumber)
					return new FormatLongCustomGenerator(context);
				else
					return new FormatLongNumGenerator(context);
			}

		}

		//[BinCustom]
		private sealed class PropertyCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//Write_Property(reader, length);
					.Snippet(BinCustom.WriteMethod ?? "Write_" + Name + "(writer)");
				return null;
			}
		}

		//[BinArray(CountFormat)] Type[] Property
		//[BinArray(CountFormat, CountCustomMethod)] Type[] Property
		private sealed class PropertyArrayGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				var cond = Statements
					//if (Property != null)
					.Condition(PropertyIsNotNull);

				var loop = cond.TrueStatements
					//var buffer = (Type[])Property;
					.Variable("buffer", Name.AsField().Cast(ItemsType))
					//writer.WriteCount((CountType)buffer.Length);
					.MethodInvoke("writer".AsArgument(), context.CountMethod, GetArrayCount.Cast(CountFormat.AsType()))
					//for(int index = 0; index < buffer.Length; index++)
					.ForCount("index", "buffer".AsVariable().Property("Length"));

				cond.FalseStatements
					//writer.WriteCount((CountType)0L);
					.MethodInvoke("writer".AsArgument(), context.CountMethod, 0L.AsExpression().Cast(CountFormat.AsType()));
				return loop.Statements;
			}

		}

		//[BinArray(CountFormat)] Collection<Type> Property
		//[BinArray(CountFormat)] CollectionType Property
		//[BinArray(CountFormat, CountCustomMethod)] Collection<Type> Property
		//[BinArray(CountFormat, CountCustomMethod)] CollectionType Property
		private sealed class PropertyCollectionGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//writer.WriteCount((CountType)Property.Count);
					.MethodInvoke("writer".AsArgument(), context.CountMethod, GetArrayCount.Cast(CountFormat.AsType()))
					//foreach(var item in Property)
					.ForEach(context.Property, "item").Statements;
			}

		}

		//[BinArray(CountCustomMethod)] Type[] Property
		//[BinArray(CountCustomMethod)] ArrayType Property
		private sealed class PropertyArrayCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
						//var buffer = (Type[])Property;
						.Variable("buffer", Name.AsField().Cast(ItemsType))
						//for(int index = 0; index < buffer.Length; index++)
						.ForCount("index", "buffer".AsVariable().Property("Length")).Statements;
			}

		}

		//[BinArray(CountCustomMethod = "Number")] Type[] Property
		//[BinArray(CountCustomMethod = "Number")] ArrayType Property
		private sealed class PropertyArrayNumGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayNumGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				//(Type[])Property
				CodeExpression bufValue = Name.AsField().Cast(ItemsType);
				if (!context.IsArrayItem)
					//(Type[])Property ?? new Type[N]
					bufValue = bufValue.Coalesce(Type.CreateArray(CountNumber));

				return Statements
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements
						//var buffer = (Type[])Property ?? new Type[N];
						.Variable("buffer", bufValue)
						//for(int index = 0; index < N; index++)
						.ForCount("index", CountNumber.AsExpression()).Statements;
			}

		}

		//[BinArray(ConditionCustomMethod)] Collection<Type> Property
		//[BinArray(ConditionCustomMethod)] CollectionType Property
		//[BinArray(CountCustomMethod)] Collection<Type> Property
		private sealed class PropertyCollectionCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				//only custom count as non number implemented
				return Statements
					//foreach(var item in Property)
					.ForEach(context.Property, "item").Statements;
			}
		}

		//[BinArray, BinArrayItem]
		private sealed class PropertyArrayItemGenerator : ClassGenerator.PropertyArrayItemGenerator
		{
			public PropertyArrayItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				var item = ClassGenerator.FormatGenerator.ResolveArrayItem(context.Property);

				Statements
					.Comment(Name)
					//item.WriteBinary(writer);
					.MethodInvoke(item, "WriteBinary", "writer".AsVariable());
				return null;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice, xItemMethod)]
		private class PropertyChoiceItemGenerator : ClassGenerator.PropertyItemGenerator
		{
			public PropertyChoiceItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				//if (Property != null) {
				var cond = Statements.Condition(PropertyIsNotNull);
				cond.TrueStatements
					.Comment(Name)
					//var item = Property;
					.Variable("item", Name.AsField())
					//Write_Item(...);
					.Snippet(context.Class.BinBlock.WriteItemMethod);

				//} else {
				return cond.FalseStatements;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Any, xItemMethod)]
		private sealed class PropertyAnyItemGenerator : PropertyChoiceItemGenerator
		{
			public PropertyAnyItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//foreach(var item in Property) {
					Statements.ForEach(context.Property, "item").Statements
						.Comment(Name)
						//Write_Item(...);
						.Snippet(context.ItemMethod);
				}
				else
				{
					var next = base.GenerateData();
					if (!context.IsRequiredItem) return null;

					//} else {
					next
						.Comment(Name)
						//var item = new Type();
						.Variable("item", RequiredItemType.CreateInvoke())
						//item.InitBinary();
						.MethodInvoke("item".AsVariable(), "InitBinary")
						//Write_Item(...);
						.Snippet(context.ItemMethod);
				}
				return null;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice)]
		private class PropertyChoiceSampleGenerator : ClassGenerator.PropertyItemGenerator
		{
			public PropertyChoiceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				//if (Property != null) {
				var cond = Statements.Condition(PropertyIsNotNull);
				cond.TrueStatements
					.Comment(Name)
					//Property.WriteBinary(writer);
					.MethodInvoke(Name.AsField(), "WriteBinary", "writer".AsVariable());

				//} else {
				return cond.FalseStatements;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Sequence)]
		private sealed class PropertySequenceSampleGenerator : PropertyChoiceSampleGenerator
		{
			public PropertySequenceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//foreach(var item in Property) {
					Statements.ForEach(context.Property, "item").Statements
						.Comment(Name)
						//item.WriteBinary(writer);
						.MethodInvoke("item".AsVariable(), "WriteBinary", "writer".AsVariable());
				}
				else
				{
					var next = base.GenerateData();
					if (!context.IsRequiredItem) return null;

					//} else {
					next
						.Comment(Name)
						//var item = new Type();
						.Variable("item", RequiredItemType.CreateInvoke())
						//item.InitBinary();
						.MethodInvoke("item".AsVariable(), "InitBinary")
						//item.WriteBinary(writer);
						.MethodInvoke("item".AsVariable(), "WriteBinary", "writer".AsVariable());
				}
				return null;
			}
		}

		private sealed class FormatContext : ClassGenerator.FormatContext
		{
			public FormatContext(ClassGenerator.PropertyContext context, BinDataAttribute binData)
				: base(context, binData) { }

			protected override string ResolveLengthMethod(out Type lenType)
			{
				return BinData.LengthFormat.ResolveWrite(out lenType);
			}

			protected override string ResolveProcessMethod(out Type arg)
			{
				return Format.ResolveWrite(out arg);
			}

			public override CodeExpression Encoder
			{
				get
				{
					if (Format.HasEncoding()) return base.Encoder;
					return "writer".AsArgument();
				}
			}
		}

		//[BinData]
		private sealed class FormatPrimitiveGenerator : ClassGenerator.FormatGenerator
		{
			public FormatPrimitiveGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//encoder.WriteProperty((Type)Property);
					.MethodInvoke(context.Encoder, context.ProcessMethod, GetItemCast);
			}
		}

		//[BinData(LengthFormat)]
		//[BinData(LengthFormat, LengthCustomMethod)]
		private sealed class FormatLongGenerator : ClassGenerator.FormatGenerator
		{
			public FormatLongGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				var cond = Statements
					.Comment(PropertyName)
					//if (Property != null) {
					.Condition(PropertyIsNotNull);
				cond.TrueStatements
					//writer.WriteLength((LengthType)((Type[])Property).Length);
					.MethodInvoke("writer".AsArgument(), context.LengthMethod, GetItemLength.Cast(LengthFormat.AsType()))
					//encoder.WriteProperty((Type[])Property);
					.MethodInvoke(context.Encoder, context.ProcessMethod, GetItemCast);
				//} else {
				cond.FalseStatements
					//writer.WriteLength((LengthType)0L);
					.MethodInvoke("writer".AsArgument(), context.LengthMethod, 0L.AsExpression().Cast(LengthFormat.AsType()));
			}
		}

		//[BinData(LengthCustomMethod)]
		private sealed class FormatLongCustomGenerator : ClassGenerator.FormatGenerator
		{
			public FormatLongCustomGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
						//encoder.WriteProperty((Type)Property);
						.MethodInvoke(context.Encoder, context.ProcessMethod, GetItemCast);
			}
		}

		//[BinData(LengthCustomMethod = "Number")]
		private sealed class FormatLongNumGenerator : ClassGenerator.FormatGenerator
		{
			public FormatLongNumGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//encoder.WriteProperty((Type)Property ?? new Type[], 0, N);
					.MethodInvoke(context.Encoder, context.ProcessMethod, GetItemCast
						.Coalesce(Format.AsType().GetElementType().CreateArray(LengthNumber)), 0, LengthNumber);
			}
		}
	}
}
